﻿using System;
using System.Drawing;
using System.Runtime.InteropServices;
using ActiproSoftware.ComponentModel;
using ActiproSoftware.Drawing;
using ActiproSoftware.SyntaxEditor;
using log4net;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using PowerGUIVsx;
using Quest.PowerGUI.ScriptEditor.Shared;

namespace PowerGuiVsx.Core
{
    internal class FontAndColorManager : IVsTextManagerEvents 
    {
        private readonly IVsFontAndColorStorage _store;
        private readonly PSSyntaxEditorControl _psControl;
        private static readonly ILog Log = LogManager.GetLogger(typeof (FontAndColorManager));
        private static Guid _categoryGuid = new Guid("A27B4E24-A735-4d1d-B8E7-9716E1E3D8E0");

        public FontAndColorManager(IVsFontAndColorStorage store, PSSyntaxEditorControl psControl)
        {
            _store = store;
            _psControl = psControl;

            uint textManagerCookie;
            var textManager = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(SVsTextManager)) as IVsTextManager2;

            IConnectionPointContainer container = textManager as IConnectionPointContainer;
            IConnectionPoint textManagerEventsConnection = null;
            Guid eventGuid = typeof(IVsTextManagerEvents).GUID;
            container.FindConnectionPoint(ref eventGuid, out textManagerEventsConnection);
            textManagerEventsConnection.Advise(this, out textManagerCookie);
        }

        public void InitializeColors()
        {
            ReloadColors();
        }

        private static class PowerGuiColors
        {
            public const string ReservedWordStyle = "ReservedWordStyle";
            public const string OperatorStyle = "OperatorStyle";
            public const string OperatorWordStyle = "OperatorWordStyle";
            public const string VariableStyle = "VariableStyle";
            public const string CmdletStyle = "CmdletStyle";
            public const string NetClassStaticStyle = "NetClassStaticStyle";
            public const string NetClassStaticMethodStyle = "NetClassStaticMethodStyle";
            public const string CmdletParamStyle = "CmdletParamStyle";
            public const string NumberStyle = "NumberStyle";
            public const string StringDelimiterStyle = "StringDelimiterStyle";
            public const string StringDefaultStyle = "StringDefaultStyle";
            public const string CommentDelimiterStyle = "CommentDelimiterStyle";
            public const string CommentDefaultStyle = "CommentDefaultStyle";
            public const string AutoVars = "AutoVars";
            public const string FunctionStyle = "FunctionStyle";
            public const string DefaultStyle = "DefaultStyle";
        }

        private static class VsColors
        {
            public const string Keyword = "PowerShellKeyword";
            public const string Operator = "PowerShellOperator";
            public const string Variable = "PowerShellVariable";
            public const string Cmdlet = "PowerShellCmdlet";
            public const string NetClassStatic = "PowerShellStaticClass";
            public const string NetClassStaticMethod = "PowerShellStaticMethod";
            public const string CmdletParam = "PowerShellCmdletParameter";
            public const string Number = "PowerShellNumber";
            public const string String = "PowerShellString";
            public const string Comment = "PowerShellComment";
            public const string AutoVars = "PowerShellAutoVar";
            public const string Function = "PowerShellFunction";
            public const string Default = "PowerShellDefault";
        }

        public void ReloadColors()
        {
            Log.Info("Reloading fonts and colors.");
            lastReload = DateTime.Now;
            if (_store.OpenCategory(ref _categoryGuid, (uint)(__FCSTORAGEFLAGS.FCSF_LOADDEFAULTS | __FCSTORAGEFLAGS.FCSF_NOAUTOCOLORS)) != VSConstants.S_OK)
            {
                Log.ErrorFormat("Failed to open font and color category [{0}].", _categoryGuid);
                return;
            }
            try
            {
                var fontAndColors = GetColors(VsColors.Default, _store);

                if (fontAndColors != null)
                { 
                    if (_psControl.RendererResolved is VisualStudio2005SyntaxEditorRenderer)
                    {
                        var renderer = (_psControl.RendererResolved as VisualStudio2005SyntaxEditorRenderer);
                        
                        renderer.TextAreaBackgroundFill = new SolidColorBackgroundFill(fontAndColors.BackgroundColor);
                        renderer.LineNumberMarginBackgroundFill = new SolidColorBackgroundFill(fontAndColors.BackgroundColor);
                        renderer.ScrollBarBlockBackgroundFill = new SolidColorBackgroundFill(fontAndColors.BackgroundColor);
                        renderer.UserMarginBackgroundFill= new SolidColorBackgroundFill(fontAndColors.BackgroundColor);
                        renderer.WordWrapMarginBackgroundFill= new SolidColorBackgroundFill(fontAndColors.BackgroundColor);
                        renderer.SplitterBackgroundFill = new SolidColorBackgroundFill(fontAndColors.BackgroundColor);
                        renderer.SelectionMarginBackgroundFill = new SolidColorBackgroundFill(fontAndColors.BackgroundColor);
                        renderer.IndicatorMarginBackgroundFill= new SolidColorBackgroundFill(fontAndColors.BackgroundColor);
                    }
                }

                SetColor(VsColors.Comment, PowerGuiColors.CommentDefaultStyle, _store);
                SetColor(VsColors.Comment, PowerGuiColors.CommentDelimiterStyle, _store);
                SetColor(VsColors.String, PowerGuiColors.StringDefaultStyle, _store);
                SetColor(VsColors.String, PowerGuiColors.StringDelimiterStyle, _store);
                SetColor(VsColors.Number, PowerGuiColors.NumberStyle, _store);
                SetColor(VsColors.Operator, PowerGuiColors.OperatorStyle, _store);
                SetColor(VsColors.Operator, PowerGuiColors.OperatorWordStyle, _store);
                SetColor(VsColors.Variable, PowerGuiColors.VariableStyle, _store);
                SetColor(VsColors.Keyword, PowerGuiColors.ReservedWordStyle, _store);
                SetColor(VsColors.Function, PowerGuiColors.FunctionStyle, _store);
                SetColor(VsColors.Cmdlet, PowerGuiColors.CmdletStyle, _store);
                SetColor(VsColors.CmdletParam, PowerGuiColors.CmdletParamStyle, _store);
                SetColor(VsColors.NetClassStatic, PowerGuiColors.NetClassStaticStyle, _store);
                SetColor(VsColors.NetClassStaticMethod, PowerGuiColors.NetClassStaticMethodStyle, _store);
                SetColor(VsColors.AutoVars, PowerGuiColors.AutoVars, _store);
                SetColor(VsColors.Default, PowerGuiColors.DefaultStyle, _store);

                var logF = new LOGFONTW[1];
                var font = new FontInfo[1];
                _store.GetFont(logF, font);

                _psControl.Font = Font.FromLogFont(logF[0]);

            }
            finally
            {
                _store.CloseCategory();
            }

        }

        private void SetColor(string vsColorName, string psColorName, IVsFontAndColorStorage store)
        {
            Log.DebugFormat("Setting PowerGUI color [{0}] from Visual Studio color [{1}].", psColorName, vsColorName);
            var index = _psControl.Language.HighlightingStyles.IndexOf(psColorName);
            var fontAndColors = GetColors(vsColorName, store);

            if (fontAndColors != null)
            {
                
                _psControl.Language.HighlightingStyles[index].Bold = fontAndColors.Bold ? DefaultableBoolean.True : DefaultableBoolean.False;
                _psControl.Language.HighlightingStyles[index].ForeColor = fontAndColors.ForegroundColor;
                _psControl.Language.HighlightingStyles[index].BackColor = fontAndColors.BackgroundColor;    
            }
        }

        private static FontAndColor GetColors(string displayItem, IVsFontAndColorStorage store)
        {
            var fontAndColor = new FontAndColor();

            var color = new ColorableItemInfo[1];

            if (store.GetItem(displayItem, color) != VSConstants.S_OK)
            {
                Log.WarnFormat("Failed to get color [{0}].", displayItem);
                return null;
            }

            if (color[0].bFontFlagsValid == 1)
            {
                fontAndColor.Bold = color[0].dwFontFlags == (uint) FONTFLAGS.FF_BOLD;
            }
            
            fontAndColor.BackgroundColor = ColorFromColorInfo(color[0].crBackground, color[0].bBackgroundValid);
            fontAndColor.ForegroundColor = ColorFromColorInfo(color[0].crForeground, color[0].bForegroundValid);

            Log.DebugFormat("Getting Visual Studio color [{0}]. {1}", displayItem, fontAndColor);

            return fontAndColor;
        }

        private static Color ColorFromColorInfo(uint color, int flags)
        {
            if (flags != 0)
            {
                return Color.FromArgb(
                    (int)((color >> 0) & 0xFF),
                    (int)((color >> 8) & 0xFF),
                    (int)((color >> 16) & 0xFF));
            }
            else
            {
                return Color.Transparent;
            }
        }

        /// <summary>
        /// Fired when an external marker type is registered.
        /// </summary>
        /// <param name="iMarkerType">[in] External marker type that was registered.</param>
        public void OnRegisterMarkerType(int iMarkerType)
        {
            
        }

        /// <summary>
        /// Fires when a view is registered.
        /// </summary>
        /// <param name="pView">[in] Pointer to the <see cref="T:Microsoft.VisualStudio.TextManager.Interop.IVsTextView"/> interface identifying the view that was registered. </param>
        public void OnRegisterView(IVsTextView pView)
        {
            
        }

        /// <summary>
        /// Fires when a view is unregistered.
        /// </summary>
        /// <param name="pView">[in] Pointer to the <see cref="T:Microsoft.VisualStudio.TextManager.Interop.IVsTextView"/> interface identifying the view that was unregistered.</param>
        public void OnUnregisterView(IVsTextView pView)
        {
            
        }

        private DateTime lastReload;

        /// <summary>
        /// Fires when the user's global preferences are changed.
        /// </summary>
        /// <param name="pViewPrefs">[in] Pointer to the <see cref="T:Microsoft.VisualStudio.TextManager.Interop.VIEWPREFERENCES"/> structure. This structure provides the current settings for the view. If this is non-null, preferences that specifically affect text view behavior have changed.</param><param name="pFramePrefs">[in] Pointer to the <see cref="T:Microsoft.VisualStudio.TextManager.Interop.FRAMEPREFERENCES"/> structure, which allows the frame to control whether the view shows horizontal or vertical scroll bars. If this is non-NULL, preferences that specifically affect code windows have changed.</param><param name="pLangPrefs">[in] Pointer to the relevant language as specified by the <paramref name="szFileType"/> and <paramref name="guidLang"/> members of the <see cref="T:Microsoft.VisualStudio.TextManager.Interop.LANGPREFERENCES"/> structure. If this is non-null, preferences that affect a specific language's common settings have changed.</param><param name="pColorPrefs">[in] Specifies color preferences. If non-null, the <paramref name="pguidColorService"/> member of the <see cref="T:Microsoft.VisualStudio.TextManager.Interop.FONTCOLORPREFERENCES"/> structure indicates which colorable item provider is associated with the <paramref name="pColorTable"/> member. If this is non-null, preferences that affect the colors or font used by a text view have changed.</param>
        public void OnUserPreferencesChanged(VIEWPREFERENCES[] pViewPrefs, FRAMEPREFERENCES[] pFramePrefs, LANGPREFERENCES[] pLangPrefs, FONTCOLORPREFERENCES[] pColorPrefs)
        {
            try 
            {
                if (null != pColorPrefs && pColorPrefs.Length > 0)
                {
                    var guid = (Guid) Marshal.PtrToStructure(pColorPrefs[0].pguidFontCategory, typeof (Guid));
                    if (guid == _categoryGuid  && DateTime.Now - lastReload > TimeSpan.FromSeconds(3))
                    {
                        ReloadColors();
                    }
                }
            } 
            catch (Exception e)
            {
                Log.Error("An exception occured when fonts and colors were being reloaded.", e);
                
            }
        }
    }
}
